#!/usr/bin/perl

######################################################
# Summary of tests run.                              #
######################################################
## Single client standard cases
# connect, QUIT, disconnect
# connect, USE, QUERY, QUIT, disconnect
# connect, USE, QUERY*n, QUIT, disconnect
# connect, disconnect
# connect, QUIT, disconnect
# connect, USE, disconnect
# connect, USE, QUERY, disconnect
# connect, USE, QUERY, USE, QUERY, QUIT, disconnect
# connect, USE, MULTIPLE_QUERIES, QUIT, disconnect
# connect, USE, QUERY_CUT, QUIT, disconnect
#
## Single client pathological cases
# connect, USE, QUERY, QUIT, USE, disconnect
# connect, QUERY, disconnect
# connect, USE, LONG_QUERY, QUIT, disconnect
# connect, USE_UNWRITABLE, QUERY_INSERT, QUIT, disconnect
# connect, USE, MALFORMED_QUERY, QUIT, disconnect
# connect, USE, UNSUPPORTED_QUERY, QUERY, QUIT, disconnect
#
## Multiple clients standard cases
# Less connections than the number of worker threads
# As many connections as the number of workers
# As many connections as the number of workers with churn
#
## Multiple clients pathological cases
# More connections than the number of workers


use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
use Time::HiRes qw(sleep time);
use IO::Socket::INET;
use File::Copy;
use Term::ANSIColor;
use List::Util qw(reduce);
use Data::Dumper;
use IO::Handle; 


my $runcmd;
my $dbfile = 'plop.db';
my $dbfile_copy = 'test.db';
my $port = int(rand(65000-1025) + 1025); # Random first port
my $repertoire = `mktemp -u /tmp/test_lif12.XXXXXXXXXX`; # nom de fichier aléatoire
chomp($repertoire);
my $vrai_repertoire = "Films";
my $nthreads = 5;


sub usage {
	die "usage: $0 command_to_run_the_server\nThe command should contain PORT, NTHREAD and DIR that will be replaced by the real port, number of threads and directory.\n";
}



sub print_success {
	# don't color \n nor empty lines
	my $text = shift;
	my @text = split "\n", $text, -1;
	print join "\n", map {$_ && colored($_, 'bold green')} @text;
}



sub print_fail {
	# don't color \n nor empty lines
	my $text = shift;
	my @text = split "\n", $text, -1;
	print join "\n", map {$_ && colored($_, 'bold red')} @text;
}



sub process_listening {
	my $pid = shift;
	my $port = shift;

	# Le processus dont on nous donne le pid est peut-être juste un shell qui a lancé la commande donnée.
	my @out = `lsof -RF -iTCP:$port -sTCP:LISTEN`;
	my ($found_pid) = grep /^p/, @out;
	my ($found_ppid) = grep /^R/, @out;

	# No output == no one listening on this port
	return 0 if (@out == 0);

	($found_pid) = ($found_pid =~ /([0-9]+)/);
	($found_ppid) = ($found_ppid =~ /([0-9]+)/);

	return ($found_pid == $pid) || ($found_ppid == $pid);
}



sub process_alive {
	my $pid = shift;
	return (kill 0, $pid);
}



sub serverthread {
	my $th = shift;
	my $mycmd = $runcmd;


	$mycmd =~ s/PORT/$th->{port}/;
	$mycmd =~ s/NTHREAD/$nthreads/;
	$mycmd =~ s/DIR/$th->{rep}/;

	print STDERR $mycmd."\n";

	$th->{pid} = open FD, "$mycmd 2>&1 |";

	if (!defined($th->{pid})) {
		print_fail "[TEST] Command `$mycmd' looks invalid.\n";
		$th->{mutexpid}->up();
		return;
	}

	$th->{mutexpid}->up();

	while (<FD>) {
		chomp;
		print "[SERVER] $_\n";
	}

	# Kill the process when the file descriptor is closed
	kill 'KILL', $th->{pid};
}



sub run_server_try_once {
	my %th;
	my $port = shift;
	my $rep = shift;

	share($th{pid});
	$th{ok} = 0;
	$th{port} = $port;
	$th{rep} = $rep;
	$th{mutexpid} = Thread::Semaphore->new(0);
	$th{thread} = threads->create(\&serverthread, \%th);


	# Wait for the pid to be available
	$th{mutexpid}->down();

	return \%th if (!defined($th{pid}));

	# During one second wait for the process to listen to the given port
	for (1 .. 3) {
		last if (process_listening($th{pid}, $port));

		# If not listening, check if still alive.
		# If dead, just warn and let the test fail.
		return \%th if (!process_alive($th{pid}));

		# Don't flood the system. :)
		sleep 0.1;
	}

	# We know it was listening a few time ago.
	# Did it crash during thread startup?
	# We need at least to establish the connection
	sleep 0.1;
	return \%th if (!process_listening($th{pid}, $port));

	# Everything should be ok
	$th{ok} = 1;

	return \%th;
}


# Try a few times in case of occupied port
sub run_server {
	# Copy the file to avoir corruption due to one test.
	copy($dbfile, $dbfile_copy);

	for (1 .. 5) {
		my $th = run_server_try_once($port, $repertoire);
		return $th if ($th->{ok});

		# Terminate the thread if still running
		$th->{thread}->kill('TERM')->join();

		# If we can't even create the process, insert coins and try again.
		last if (!defined($th->{pid}));

		# Didn't work? Try another port.
		$port++;
	}

	print_fail("Server don't listen to the port\n");
	unlink('test.db');
	return undef;
}



sub stop_server {
	my $th = shift;
	my $ret;

	# Just simulate a Ctrl+C when terminating the server.
	$ret = kill 'INT', $th->{pid};
	sleep 0.2 if ($ret);

	# Be more directive, send a SIGTERM
	$ret = kill 'TERM', $th->{pid};
	sleep 0.2 if ($ret);

	# Stop NOW I said!
	$ret = kill 'KILL', $th->{pid};
	sleep 0.5 if ($ret);


	# Thread should already be finished
#	$th->{thread}->kill('KILL')->join();
	$th->{thread}->join();
	unlink($dbfile_copy);
}



sub connection_new {
	my $port = shift;
	my $sock = IO::Socket::INET->new(PeerAddr => 'localhost', PeerPort => $port, Proto => 'tcp', Blocking => 0);
	print "$!\n" if (!defined($sock));
	return $sock;
}


sub connection_close {
	close($_[0]) or die($!);
}



# Read a line (until $/ is found) within up to a certain amout of time
sub get_line_timed {
	my $fd = shift;
	my $timeout = shift;
	my $start_time = time;
	my $end_time = $start_time + $timeout;
	my ($rin, $rout);
	my $closed = 0;
	my $timeouted = 0;
	my $c = '';
	my $strout = '';

	$rin = "";
	vec($rin, fileno($fd), 1) = 1;

	while ($c ne $/) {
		my $nfound;

		my $now = time;
		last if ($now > $end_time);

		$nfound = select($rout = $rin, undef, undef, $end_time - $now);

		# Unexpected error
		die $! if ($nfound == -1);

		# Timeouted
		if ($nfound == 0) {
			$timeouted = 1;
			last;
		}

		$! = 0;
		my $nread = sysread($fd, $c, 1);

		# socket closed?
		if (!defined($nread)) {
			next if ($!{EAGAIN});
			$closed = 1 if ($!{ECONNRESET});
			last;
		}

		# WTF? Nothing to read but select returned...
		if ($nread == 0) {
			warn "Bug in get_line_timed. select returned without anything to read\n";
			$closed = 1;
			last;
		}

		$strout .= $c;
	}

	print STDERR $strout."\n";

	return ($strout, $timeouted, $closed);
}


# Read a given number of byte and save it in a file (within up to a certain amout of time)
sub get_bytes_timed {
    my $taillebuff = 100;
    my $fd = shift;
    my $fd_file = shift;
    my $nb_bytes = shift;
    my $timeout = shift;
    my $start_time = time;
    my $end_time = $start_time + $timeout;
    my ($rin, $rout);
    my $closed = 0;
    my $timeouted = 0;
    my $buff = "";
    my $strout = '';
    my $nb_lus = 0;
    
    $rin = "";
    vec($rin, fileno($fd), 1) = 1;
    
    $nb_lus = 0;
    while ($nb_lus < $nb_bytes) {
	my $nfound;
	
	my $now = time();
	last if ($now > $end_time);
	
	$nfound = select($rout = $rin, undef, undef, $end_time - $now);
	
	# Unexpected error
	die $! if ($nfound == -1);
	
	# Timeouted
	if ($nfound == 0) {
	    $timeouted = 1;
	    last;
	}
	
	$! = 0;
	my $nread = sysread($fd, $buff, $taillebuff);
	    
	# socket closed?
	if (!defined($nread)) {
	    next if ($!{EAGAIN});
	    $closed = 1 if ($!{ECONNRESET});
	    last;
	}
	
	# WTF? Nothing to read but select returned...
	if ($nread == 0) {
	    warn "Bug in get_line_timed. select returned without anything to read\n";
	    $closed = 1;
	    last;
	}

	syswrite ($fd_file, $buff, $nread);
	$nb_lus = $nb_lus + $nread;
    }
    
    print STDERR "$nb_lus bytes read\n";
    
    return ($nb_lus, $timeouted, $closed);
}


# Wait for a line on $cnx starting with $str swallowing every intermediate lines
# return ($ok, $timeouted, $closed, $errmsg, @eatenlines)
sub expect_line {
	my ($ok, $timeouted, $closed, $errmsg, $line);
	my @ign;

	# Iterate through lines as long as there are some.
	do {
		($ok, $timeouted, $closed, $errmsg, $line) = expect_line_now(@_);
		push @ign, $line if ($line);
	} while (!$ok && !$timeouted && !$closed);

	return ($ok, $timeouted, $closed, $errmsg, @ign);
}



# Check whether the next line on $cnx starts with $str
# return ($ok, $timeouted, $closed, $errmsg, $line)
# $errmsg contains an error message, its content is "success" if everything goes well
# $line contains the read line if any
sub expect_line_now {
	my $cnx = shift;
	my $str = shift;
	my $errmsg = "";
	my ($strout, $timeouted, $closed) = get_line_timed($cnx, 1);

	# If we found a \n we know the connection isn't closed and we didn't timeouted

	if (substr($strout, -1) ne "\n") {
		if ($strout ne "") {
			$errmsg = "We found `$strout' without a ending \\n while we expected `$str'\n";
		} else {
			$errmsg = "The expected `$str' didn't come before the timeout\n";
		}
		return (0, $timeouted, $closed, $errmsg, $strout);
	}

	chomp $strout;

	my $ok = int($strout =~ /^$str/i);
	if (!$ok) {
		$errmsg = "We found `$strout' while we expected `$str'\n";
	} else {
		$errmsg = "success";
	}

	return ($ok, $timeouted, $closed, $errmsg, $strout);
}



# Look for the next line starting with $str swallowing every intermediate line
# Plus it outputs errors or success messages.
# return 0 or 1 upon fail or success
sub checked_expect_line {
	my $cnx = shift;
	my $str = shift;
	my ($ok, $timeouted, $closed, $errmsg, @ignored) = expect_line($cnx, $str);

	if ($ok) {
		print_success("[OK] ".$ignored[-1]."\n") if ($ok);
	} else {
		print_fail($errmsg);
		if ($timeouted) {
			print_fail("`$str' never came\n");
			print_fail("Here are the ignored lines :\n");
			map {print_fail("-> $_\n")} @ignored;
		}
		print_fail("Connection was closed unexpectedly\n") if ($closed);
	}

	return $ok;
}


# Check if the immediate next line starts with $str
# Plus it outputs errors or success messages.
# return 0 or 1 upon fail or success
sub checked_expect_line_now {
    my $cnx = shift;
    my $str = shift;
    my ($ok, $timeouted, $closed, $errmsg, $line) = expect_line_now($cnx, $str);
    
    if ($ok) {
	my $display_str = $line;
	if (length($display_str) > 75) {
	    my $display_len = length($display_str);
	    $display_str = substr($display_str, 0, 75);
	    $display_str .= "... +".($display_len - 75)." bytes more";
	}
	
	print_success("[OK] $display_str\n") if ($ok);
	
    } else {
	print_fail($errmsg);
	print_fail("`$str' There was nothing to read\n") if ($timeouted);
	print_fail("Connection was closed unexpectedly\n") if ($closed);
    }
    
    return $ok;
}


# Check if the immediate next line starts with $str
# Plus it outputs errors or success messages.
# return 0 or 1 upon fail or success and the line too
sub checked_expect_line_now_with_line {
    my $cnx = shift;
    my $str = shift;
    my ($ok, $timeouted, $closed, $errmsg, $line) = expect_line_now($cnx, $str);
    
    if ($ok) {
	my $display_str = $line;
	if (length($display_str) > 75) {
	    my $display_len = length($display_str);
	    $display_str = substr($display_str, 0, 75);
	    $display_str .= "... +".($display_len - 75)." bytes more";
	}
	
	print_success("[OK] $display_str\n") if ($ok);
	
    } else {
	print_fail($errmsg);
	print_fail("`$str' There was nothing to read\n") if ($timeouted);
	print_fail("Connection was closed unexpectedly\n") if ($closed);
    }

    

    return ($ok, $line);
}


# Send a string on a socket and check the errors
sub checked_send {
	my $cnx = shift;
	my $str = shift;
	my $want_success = shift;

	my $display_str = $str;
	my $full_length = length($str);
	my $closed = 0;
	my ($win, $wout);
	my $ret;


	$want_success = 1 if (!defined($want_success));

	chomp $display_str;

	if (length($display_str) > 75) {
		my $display_len = length($display_str);
		$display_str = substr($display_str, 0, 75);
		$display_str .= "... +".($display_len - 75)." bytes more (total : $full_length bytes)";
	}

	print "[sending] $display_str\n";


	$win = "";
	vec($win, fileno($cnx), 1) = 1;

	while ($full_length > 0) {
		my $nfound = select(undef, $wout = $win, undef, undef);
		# Unexpected error
		die $! if ($nfound == -1);

		$! = 0;
		my $nwrite = syswrite($cnx, $str);

		# socket closed?
		if (!defined($nwrite)) {
			next if ($!{EAGAIN});
			$closed = 1 if ($!{ECONNRESET});
			last;
		}

		# WTF? Nothing written but select returned...
		if ($nwrite == 0) {
			warn "Bug in checked_send. select returned without anything written\n";
			$closed = 1;
			last;
		}

		if (defined($nwrite)) {
			$str = substr($str, $nwrite);
			$full_length -= $nwrite;
		}
	}


	# want success and socket not closed => success
	# want fail and socket closed => success
	return 1 if ($want_success == !$closed);

	# Connection closed (and we wanted to succed)
	print_fail("$!\n") if ($closed);
	print_fail("Write should have returned an error\n") if (!$closed);

	return 0;
}




sub run_test {
	my $test = shift;
	my $retval = 0;
	my $thread = run_server();

	# Server didn't start -> EPIC FAIL
	exit 1 if (!defined($thread));

	# If we were asked to handle connection, pass the socket file descriptor to the test function
	# Otherwise, pass the thread for the test function to create the connections itself
	if (!$test->{nocnx}) {
		my $cnx = connection_new($thread->{port});

		# If not defined (no connection), the only possible reason is that the server crashed
		# since we know it was listening.
		# This is checked below.
		# Plus, $retval is already 0 (meaning FAIL)
		if (defined($cnx)) {
			$retval = $test->{func}->($cnx);
			connection_close($cnx);
		}

		sleep 0.2;
		print_fail("Server crashed when closing the connection\n") if (!process_alive($thread->{pid}));
	} else {
		$retval = $test->{func}->($thread);

		sleep 0.2;
	print_fail("Server did not survived the test\n") if (!process_alive($thread->{pid}));
	}

	stop_server($thread);
	return $retval;
}




################################
# Single client standard cases #
################################

# connect, USE, QUIT, disconnect
sub test1 {
	my $cnx = shift;
	
	print "[TEST] connect, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, USE, QUERY, QUIT, disconnect
sub test2 {
	my $cnx = shift;

	print "[TEST] connect, USE, ERREUR, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "LIS\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT LIS"));
	return 0 if (!checked_expect_line_now($cnx, "FAIL"));
	return 0 if (!checked_send($cnx, "GETK\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT GETK"));
	return 0 if (!checked_expect_line_now($cnx, "FAIL"));
	return 0 if (!checked_send($cnx, "LISTfichier\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT LISTfichier"));
	return 0 if (!checked_expect_line_now($cnx, "FAIL"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}



# connect, USE, QUERY*n, QUIT, disconnect
sub test3 {
	my $cnx = shift;

	print "[TEST] connect, USE, texte*n, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));

#	my $text_base = "un text difficile et long pour causer des problèmes 1234567890°+&é\"'(-è_çà)=#{[|^@]}";
	my $text_base = "un text difficile et long pour causer des problèmes 1234567890°&é'-è`_çà";
	my $text = $text_base;
	my $i;
	for ($i=0; $i<100; $i++) {
	    if (($i % 10) == 0) {
		return 0 if (!checked_send($cnx, $text."\n"));
		return 0 if (!checked_expect_line_now($cnx, "RCPT $text"));
		return 0 if (!checked_expect_line_now($cnx, "FAIL"));
	    }
	    $text .= " et en plus il se poursuit par ".$text_base;
	}
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, disconnect
sub test4 {
	my $cnx = shift;

	print "[TEST] connect, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 1;
}

# connect, LIST, QUIT, disconnect
sub test5 {
	my $cnx = shift;

	print "[TEST] connect, LIST, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "LIST\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT LIST"));
	return 0 if (!checked_expect_line_now($cnx, "FICH (checksum.md5|Les étudiants présentent Spiral connect.flv|lien.flv|plop.txt)"));
	return 0 if (!checked_expect_line_now($cnx, "FICH (checksum.md5|Les étudiants présentent Spiral connect.flv|lien.flv|plop.txt)"));
	return 0 if (!checked_expect_line_now($cnx, "FICH (checksum.md5|Les étudiants présentent Spiral connect.flv|lien.flv|plop.txt)"));
	return 0 if (!checked_expect_line_now($cnx, "FICH (checksum.md5|Les étudiants présentent Spiral connect.flv|lien.flv|plop.txt)"));
	return 0 if (!checked_expect_line_now($cnx, "ENDL"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, LIST, GETF, QUIT, disconnect
sub test6 {
	my $cnx = shift;

	print "[TEST] connect, LIST, GETF, FREE, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "LIST\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT LIST"));
	return 0 if (!checked_expect_line($cnx, "ENDL"));
	return 0 if (!checked_send($cnx, "GETF plop.txt\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT"));
	return 0 if (!checked_expect_line_now($cnx, "SIZE "));
	return 0 if (!checked_expect_line_now($cnx, "ceci est un texte"));
	return 0 if (!checked_send($cnx, "FREE plop.txt\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT"));
	return 0 if (!checked_expect_line_now($cnx, "FREE"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, USE, QUERY, disconnect
sub test7 {
	my $cnx = shift;

	print "[TEST] connect, GETF, GETF, FREE, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "LIST\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT LIST"));
	return 0 if (!checked_expect_line($cnx, "ENDL"));
	return 0 if (!checked_send($cnx, "GETF plop.txt\n"));
#	return 0 if (!checked_expect_line_now($cnx, "RCPT"));
#	return 0 if (!checked_expect_line_now($cnx, "SIZE "));
	return 0 if (!checked_expect_line($cnx, "ceci est un texte"));
	print "[TEST] another GETF should work\n";
	return 0 if (!checked_send($cnx, "GETF plop.txt\n"));
#	return 0 if (!checked_expect_line_now($cnx, "RCPT"));
#	return 0 if (!checked_expect_line_now($cnx, "SIZE "));
	return 0 if (!checked_expect_line($cnx, "ceci est un texte"));
	return 0 if (!checked_send($cnx, "FREE plop.txt\n"));
	return 0 if (!checked_expect_line($cnx, "FREE"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, USE, QUERY, USE, QUERY, QUIT, disconnect
sub test8 {
	my $cnx = shift;
	my  $i;

	print "[TEST] connect, USE, multiple(GETF + FREE), QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	for ($i=0; $i<100; $i++) {
	    print "[TEST] GETF $i/100 should work\n";
	    return 0 if (!checked_send($cnx, "GETF plop.txt\n"));
	    return 0 if (!checked_expect_line($cnx, "ceci est un texte"));
	    return 0 if (!checked_send($cnx, "FREE plop.txt\n"));
	    return 0 if (!checked_expect_line($cnx, "FREE"));
	}
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

sub get_file {
    my $cnx = shift;
    my $file_name = shift;

    my $ok;
    my $line;
    my $taille;
    my $nb_lus;
    my $timeouted;
    my $closed;
    my $fd_file;

    return 0 if (!checked_send($cnx, "GETF $file_name\n"));
    return 0 if (!checked_expect_line_now($cnx, "RCPT GETF $file_name"));

    ($ok, $line) = checked_expect_line_now_with_line($cnx, "SIZE");
    $line =~ s/[\r\n]//;
    
    return 0 if (!$ok);
    if ($line !~ /^SIZE [0-9]+$/) {
	printf STDERR "The response $line sould contains the size of file\n";
	return 0;
    }
    $taille = $line;
    $taille =~ s/^SIZE ([0-9]+)$/$1/;
    printf STDERR "taille = #$taille#\n";

#    sysopen($fd_file, "./$file_name", O_RDWR|O_CREAT) or die ("sysopen $file_name: $!");
    open($fd_file, "> ./$file_name") or die ("sysopen $file_name: $!");
    ($nb_lus, $timeouted, $closed) = get_bytes_timed($cnx, $fd_file, $taille, 10);

    
    if ($nb_lus != $taille) {
	print_fail("We get $nb_lus bytes instead of $taille\n");
	return 0;
    }
    if ($timeouted) {
	print_fail("End of file never came\n");
	return 0;
    }
    if ($closed) {
	print_fail("Connection was closed unexpectedly\n") if ($closed);
	return 0;
    }
    print_success("[OK] \n");
    return 1;
}

# connect, USE, MULTIPLE_QUERIES, QUIT, disconnect
sub test9 {
	my $cnx = shift;
	my $res_check;
	my @list_check;
	my $good;
	my $ligne;

	print "[TEST] connect, multiple(GETF big file, FREE big file), QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	
	return 0 if (!get_file($cnx, "lien.flv"));

	$res_check = `md5sum -c Films/checksum.md5 2> /dev/null | grep lien.flv`;
	print STDERR "#$res_check#\n";
	
	if ($res_check !~ /lien.flv: OK/) {
	    print_fail("The checksum of the file is wrong !! $res_check\n");
	    return 0;
	}
	
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, USE, MULTIPLE_QUERIES, QUIT, disconnect
sub test10 {
	my $cnx = shift;
	my $res_check;
	my @list_check;
	my $good;
	my $ligne;

	print "[TEST] connect, GETF wrong name, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "GETF wrong file name\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT"));
	return 0 if (!checked_expect_line_now($cnx, "NONE"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}




####################################
# Single client pathological cases #
####################################

# connect, USE, QUERY, QUIT, USE, disconnect
sub test11 {
	my $cnx = shift;

	print "[TEST] connect, GETF step by step, FREE, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "GETF "));
	sleep 0.2;
	return 0 if (!checked_send($cnx, "plop.txt"));
	sleep 0.2;
	return 0 if (!checked_send($cnx, "\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT GETF plop.txt"));
	return 0 if (!checked_expect_line_now($cnx, "SIZE "));
	return 0 if (!checked_expect_line($cnx, "ceci est un texte"));
	return 0 if (!checked_send($cnx, "FREE plop.txt\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT FREE plop.txt"));
	return 0 if (!checked_expect_line_now($cnx, "FREE"));
	return 0 if (!checked_send($cnx, "QUIT\n"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, QUERY, disconnect
sub test12 {
	my $cnx = shift;

	print "[TEST] connect, QUERY, QUIT+garbage disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "HELO"));
	return 0 if (!checked_send($cnx, "LIST\n"));
	return 0 if (!checked_send($cnx, "QUIT\nplopplop"));
	return 0 if (!checked_expect_line($cnx, "ENDL"));
	return 0 if (!checked_expect_line_now($cnx, "RCPT QUIT"));
	return 0 if (!checked_expect_line_now($cnx, "OLEH"));
	return 1;
}

# connect, USE, LONG_QUERY, QUIT, disconnect
sub test13 {
	my $cnx = shift;

	print "[TEST] connect, USE, LONG_QUERY, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "#HELLO"));
	return 0 if (!checked_send($cnx, "USE $dbfile_copy;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_send($cnx, "SELECT 0".("+1"x(1024*1024)).";\n"));
	return 0 if (!checked_expect_line_now($cnx, "#ERR"));
	return 0 if (!checked_send($cnx, "QUIT;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line_now($cnx, "#BYE"));
	return 1;
}

# connect, USE_UNWRITABLE, QUERY_INSERT, QUIT, disconnect
sub test14 {
	my $cnx = shift;

	print "[TEST] connect, USE_UNWRITABLE, QUERY_INSERT, QUIT disconnect\n";

	chmod 0400, $dbfile_copy;

	return 0 if (!checked_expect_line_now($cnx, "#HELLO"));
	return 0 if (!checked_send($cnx, "USE $dbfile_copy;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_send($cnx, "CREATE TABLE toto_people (id INT, name VARCHAR(100));\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line($cnx, "#ERR"));
	return 0 if (!checked_send($cnx, "QUIT;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line_now($cnx, "#BYE"));
	return 1;
}

# connect, USE, MALFORMED_QUERY, QUIT, disconnect
sub test15 {
	my $cnx = shift;

	print "[TEST] connect, USE, MALFORMED_QUERY, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "#HELLO"));
	return 0 if (!checked_send($cnx, "USE $dbfile_copy;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_send($cnx, "SELECT * FROM;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line($cnx, "#ERR"));
	return 0 if (!checked_send($cnx, "QUIT;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line_now($cnx, "#BYE"));
	return 1;
}

# connect, USE, UNSUPPORTED_QUERY, QUERY, QUIT, disconnect
sub test16 {
	my $cnx = shift;

	print "[TEST] connect, USE, UNSUPPORTED_QUERY, QUIT, disconnect\n";

	return 0 if (!checked_expect_line_now($cnx, "#HELLO"));
	return 0 if (!checked_send($cnx, "USE $dbfile_copy;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_send($cnx, "DELETE FROM ues WHERE 1=0;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line($cnx, "#ERR"));
	return 0 if (!checked_send($cnx, "SELECT 2+4;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line($cnx, "#OK"));
	return 0 if (!checked_send($cnx, "QUIT;\n"));
	return 0 if (!checked_expect_line_now($cnx, "#RECU"));
	return 0 if (!checked_expect_line_now($cnx, "#BYE"));
	return 1;
}


###################################
# Multiple clients standard cases #
###################################

# Less connections than the number of worker threads
sub test17 {
	my $thread = shift;

	print "[TEST] Less connections than the number of worker threads\n";

	my @cnx = map {connection_new($thread->{port})} (2 .. $nthreads);
	my @opencnx = grep {defined} @cnx;

	# if one connection failed, the test failed
	if (@opencnx != @cnx) {
		map {connection_close($_)} @opencnx;
		return 0;
	}

	# Could potentially use any mono-client test
	my @threads = map {async {sleep(rand(0.3)); test5($_)}} (@cnx);

	# Gather results and close connections
	my @res = map {$_->join()} @threads;
	map {connection_close($_)} @cnx;

	# Return 1 if every thread returned 1
	return (reduce {$a && $b} (@res));
}

# As many connections as the number of workers
sub test18 {
	my $thread = shift;

	print "[TEST] As many connections than the number of worker threads\n";

	my @cnx = map {connection_new($thread->{port})} (1 .. $nthreads);
	my @opencnx = grep {defined} @cnx;

	# if one connection failed, the test failed
	if (@opencnx != @cnx) {
		print_fail("$!\n");
		map {connection_close($_)} @opencnx;
		return 0;
	}

	# Could potentially use any mono-client test
	my @threads = map {async {sleep(rand(0.6)); test2($_)}} (@cnx);
	my @res = map {$_->join()} @threads;
	map {connection_close($_)} @cnx;

	# Return 1 if every thread returned 1
	return (reduce {$a && $b} (@res));
}

# As many connections as the number of workers with churn
sub test19_aux {
	my $port = shift;

	# Repeat 5 times
	foreach (1 .. 5) {
		my $cnx = connection_new($port);

		if (!defined($cnx)) {
			print_fail("$!\n");
			return 0;
		}

		# Wait a bit in order not to synchronize everything
		sleep(rand(0.6));

		# Should we call a random test?
		return 0 if (!test5($cnx));

		connection_close($cnx);

		# FIXME: This sleep is here only because the server wait before closing the connection
		sleep 1.3;
	}

	return 1;
}

sub test19 {
	my $thread = shift;

	print "[TEST] As many connections as the number of worker with chrun\n";

	# Each test19_aux will create one connection at time and repeat it 5 times
	my @threads = map {async {sleep(rand(0.6)); test19_aux($thread->{port})}} (1 .. $nthreads);

	# Gather results
	my @res = map {$_->join()} @threads;

	# Return 1 if every thread returned 1
	return (reduce {$a && $b} (@res));
}

#######################################
# Multiple clients pathological cases #
#######################################
# More connections than the number of workers
sub test20 {
	my $thread = shift;

	print "[TEST] More connections than the number of workers\n";
	print colored("[INFO] This test MUST output some timeout errors. Look at the final result to know if it failed.", 'bold yellow')."\n";

	my @cnx = map {connection_new($thread->{port})} (1 .. ($nthreads * 2));
	my @opencnx = grep {defined} @cnx;

	# if one connection failed, the test failed
	if (@opencnx != @cnx) {
		print_fail("$!\n");
		map {connection_close($_)} @opencnx;
		return 0;
	}

	# Could potentially use any mono-client test
	my @threads = map {async {sleep(rand(0.6)); test5($_)}} (@cnx);
	my @res = map {$_->join()} @threads;
	map {connection_close($_)} @cnx;

	# Extract from the result list, those who should succed and those who should fail
	my @ok_res = @res[0 .. ($nthreads - 1)];
	my @fail_res = @res[$nthreads .. $#res];
	return ((reduce {$a && $b} (@ok_res)) && !(reduce {$a || $b} (@fail_res)));
}


my @testlist = (
    {func => \&test1},
    {func => \&test2},
    {func => \&test3},
    {func => \&test4},
    {func => \&test5},
    {func => \&test6},
    {func => \&test7},
    {func => \&test8},
    {func => \&test9},
    {func => \&test10},
    {func => \&test11},
    {func => \&test12},
	# {func => \&test13},
	# {func => \&test14},
	# {func => \&test15},
	# {func => \&test16},
   {func => \&test17, nocnx => 1},
   {func => \&test18, nocnx => 1},
   {func => \&test19, nocnx => 1},
    # {func => \&test20, nocnx => 1}
);

sub main {
	usage() if (@ARGV == 0);

	my $cmd = "cp -a $vrai_repertoire $repertoire";
	my $res = system($cmd);
	if ($res) {
	    die("problem with copy of $vrai_repertoire in $repertoire by command $cmd");
	}

	
	$runcmd = join ' ', @ARGV;

	map {
	    if (run_test($_)) {
		print_success("[TEST] OK\n\n")
	    } else {
		print_fail("[TEST] FAILED\n\n")
	}
	} @testlist;
}




main();
